import os
import warnings
from typing import List, Callable

import numpy as np
import scipy
import enum

from manim import SVGMobject, VGroup, VMobject, Mobject, Text, ArcBetweenPoints, Arc
from manim import Circle, Line, RoundedRectangle, Rectangle, ArcPolygon
from manim.utils.color import BLUE_E, BLACK, WHITE, GREY, GREY_BROWN, BLUE
from manim.utils.space_ops import normalize
from manim.constants import UL, DOWN, LEFT, PI, LARGE_BUFF, MED_SMALL_BUFF, RIGHT, ORIGIN, UP, UR, DL, DR

from .svg_drawing import ThoughtBubble, SVG_DIR

__all__ = [
    'PiCreature',
    'PiCreatureGroup',
    'Yan',
    'SmallYan',
    'Eyes',
    'ThoughtBubble',
    "SVGYan",
]

PI_CREATURE_DIR = SVG_DIR
PI_CREATURE_SCALE_FACTOR = 0.5


class PIBodyPart(enum.IntEnum):
    LEFT_EYE_INDEX = 0
    RIGHT_EYE_INDEX = 1
    LEFT_PUPIL_INDEX = 2
    RIGHT_PUPIL_INDEX = 3
    BODY_INDEX = 4
    MOUTH_INDEX = 5


get_norm: Callable[[List], float] = lambda vec: scipy.linalg.norm(vec)


class PiCreature(SVGMobject):
    def __init__(self, mode="plain", color=BLUE_E, file_name_prefix="PiCreatures", stroke_width=0,
                 stroke_color=BLACK, fill_opacity=1.0, height=3, corner_scale_factor=0.75,
                 flip_at_start=False, is_looking_direction_purposeful=False, start_corner=None,
                 pupil_to_eye_width_ratio=0.4,
                 pupil_dot_to_pupil_width_ratio=0.3, **kwargs):
        self.mode = mode
        self.file_name_prefix = file_name_prefix
        self.corner_scale_factor = corner_scale_factor
        self.flip_at_start = flip_at_start
        self.is_looking_direction_purposeful = is_looking_direction_purposeful
        self.start_corner = start_corner
        # Range of proportions along body where arms are
        self.right_arm_range = [0.55, 0.7]
        self.left_arm_range = [.34, .462]
        self.pupil_to_eye_width_ratio = pupil_to_eye_width_ratio
        self.pupil_dot_to_pupil_width_ratio = pupil_dot_to_pupil_width_ratio

        self.parts_named = False

        svg_file = os.path.join(
            PI_CREATURE_DIR, "%s_%s.svg" % (self.file_name_prefix, mode)
        )
        if not os.path.exists(svg_file):
            warnings.warn("No %s design with mode %s" %
                          (self.file_name_prefix, mode))
            # TODO, this needs to change to a different, better directory
            svg_file = os.path.join(
                SVG_DIR,
                "PiCreatures_plain.svg",
            )
        SVGMobject.__init__(self, file_name=svg_file, height=height, stroke_width=stroke_width,
                            fill_opacity=fill_opacity, color=color, stroke_color=stroke_color,
                            **kwargs)

        if self.flip_at_start:
            self.flip()
        if self.start_corner is not None:
            self.to_corner(self.start_corner)

    def align_data(self, mobject, skip_point_alignment=True):
        # This ensures that after a transform into a different mode,
        # the pi creatures mode will be updated appropriately
        SVGMobject.align_data(self, mobject, skip_point_alignment=True)
        if isinstance(mobject, PiCreature):
            self.mode = mobject.get_mode()

    def name_parts(self):
        self.mouth = self.submobjects[PIBodyPart.MOUTH_INDEX]
        self.body = self.submobjects[PIBodyPart.BODY_INDEX]
        self.pupils = VGroup(*[
            self.submobjects[PIBodyPart.LEFT_PUPIL_INDEX],
            self.submobjects[PIBodyPart.RIGHT_PUPIL_INDEX]
        ])
        self.eyes = VGroup(*[
            self.submobjects[PIBodyPart.LEFT_EYE_INDEX],
            self.submobjects[PIBodyPart.RIGHT_EYE_INDEX]
        ])
        self.eye_parts = VGroup(self.eyes, self.pupils)
        self.parts_named = True

    def init_colors(self):
        SVGMobject.init_colors(self)
        if not self.parts_named:
            self.name_parts()
        self.mouth.set_fill(BLACK, opacity=1)
        self.body.set_fill(self.color, opacity=1)
        self.eyes.set_fill(WHITE, opacity=1)
        # self.eyes.set(height=0.1*self.height)
        self.init_pupils()
        return self

    def init_pupils(self):
        # Instead of what is drawn, make new circles.
        # This is mostly because the paths associated
        # with the eyes in all the drawings got slightly
        # messed up.
        for eye, pupil in zip(self.eyes, self.pupils):
            pupil_r = eye.get_width() / 2
            pupil_r *= self.pupil_to_eye_width_ratio
            dot_r = pupil_r
            dot_r *= self.pupil_dot_to_pupil_width_ratio

            new_pupil = Circle(
                radius=pupil_r,
                color=BLACK,
                fill_opacity=1,
                stroke_width=0,
            )
            dot = Circle(
                radius=dot_r,
                color=WHITE,
                fill_opacity=1,
                stroke_width=0,
            )
            new_pupil.move_to(pupil)
            pupil.become(new_pupil)
            dot.shift(
                new_pupil.get_boundary_point(UL) -
                dot.get_boundary_point(UL)
            )
            pupil.add(dot)

    def copy(self):
        copy_mobject = SVGMobject.copy(self)
        copy_mobject.name_parts()
        return copy_mobject

    def set_color(self, color):
        self.body.set_fill(color)
        self.color = color
        return self

    def change_mode(self, mode):
        new_self = self.__class__(
            mode=mode
        )
        new_self.match_style(self)
        new_self.match_height(self)
        if self.is_flipped() != new_self.is_flipped():
            new_self.flip()
        new_self.shift(self.eyes.get_center() - new_self.eyes.get_center())
        if hasattr(self, "purposeful_looking_direction"):
            new_self.look(self.purposeful_looking_direction)
        self.become(new_self)
        self.mode = mode
        return self

    def get_mode(self):
        return self.mode

    def look(self, direction):
        norm = get_norm(direction)
        if norm == 0:
            return
        direction /= norm
        self.purposeful_looking_direction = direction
        for pupil, eye in zip(self.pupils.split(), self.eyes.split()):
            c = eye.get_center()
            right = eye.get_right() - c
            up = eye.get_top() - c
            vect = direction[0] * right + direction[1] * up
            v_norm = get_norm(vect)
            p_radius = 0.5 * pupil.get_width()
            vect *= (v_norm - 0.75 * p_radius) / v_norm
            pupil.move_to(c + vect)
        self.pupils[1].align_to(self.pupils[0], DOWN)
        return self

    def look_at(self, point_or_mobject):
        if isinstance(point_or_mobject, Mobject):
            point = point_or_mobject.get_center()
        else:
            point = point_or_mobject
        self.look(point - self.eyes.get_center())
        return self

    def change(self, new_mode, look_at_arg=None):
        self.change_mode(new_mode)
        if look_at_arg is not None:
            self.look_at(look_at_arg)
        return self

    def get_looking_direction(self):
        vect = self.pupils.get_center() - self.eyes.get_center()
        return normalize(vect)

    def get_look_at_spot(self):
        return self.eyes.get_center() + self.get_looking_direction()

    def is_flipped(self):
        return self.eyes.submobjects[0].get_center()[0] > \
               self.eyes.submobjects[1].get_center()[0]

    def blink(self):
        eye_parts = self.eye_parts
        eye_bottom_y = eye_parts.get_bottom()[1]
        eye_parts.apply_function(
            lambda p: [p[0], eye_bottom_y, p[2]]
        )
        return self

    def to_corner(self, vect=None, **kwargs):
        if vect is not None:
            SVGMobject.to_corner(self, vect, **kwargs)
        else:
            self.scale(self.corner_scale_factor)
            self.to_corner(DOWN + LEFT, **kwargs)
        return self

    def get_bubble(self, *content, **kwargs):
        bubble_class = kwargs.get("bubble_class", ThoughtBubble)
        bubble = bubble_class(**kwargs)
        if len(content) > 0:
            if isinstance(content[0], str):
                content_mob = Text(*content)
            else:
                content_mob = content[0]
            bubble.add_content(content_mob)
            if "height" not in kwargs and "width" not in kwargs:
                bubble.resize_to_content()
        bubble.pin_to(self)
        self.bubble = bubble
        return bubble

    def make_eye_contact(self, pi_creature):
        self.look_at(pi_creature.eyes)
        pi_creature.look_at(self.eyes)
        return self

    def shrug(self):
        self.change_mode("shruggie")
        top_mouth_point, bottom_mouth_point = [
            self.mouth.points[np.argmax(self.mouth.points[:, 1])],
            self.mouth.points[np.argmin(self.mouth.points[:, 1])]
        ]
        self.look(top_mouth_point - bottom_mouth_point)
        return self

    def get_arm_copies(self):
        body = self.body
        return VGroup(*[
            body.copy().pointwise_become_partial(body, *alpha_range)
            for alpha_range in (self.right_arm_range, self.left_arm_range)
        ])

    def smile(self, smile_mode):
        mouth_parts = self.mouth
        mouth_radius = mouth_parts.get_width() / 2
        # Instead of what is drawn, make new mouth.
        if smile_mode == 0:
            new_mouth = ArcBetweenPoints(start=mouth_parts.get_left(), end=mouth_parts.get_right(),
                                         color=BLACK, radius=4 * mouth_radius)
        else:
            ang = -PI if smile_mode == 1 else PI
            new_mouth = Arc(radius=mouth_radius, angle=ang, stroke_width=5, color=BLACK)
        new_mouth.move_to(mouth_parts)
        mouth_parts.become(new_mouth)
        return self


def get_all_pi_creature_modes():
    result = []
    prefix = "%s_" % PiCreature.file_name_prefix
    suffix = ".svg"
    for file in os.listdir(PI_CREATURE_DIR):
        if file.startswith(prefix) and file.endswith(suffix):
            result.append(
                file[len(prefix):-len(suffix)]
            )
    return result


class Randolph(PiCreature):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        pass  # Nothing more than an alternative name


class Mortimer(PiCreature):
    def __init__(self, flip_at_start=True, color=GREY_BROWN, **kwargs):
        super().__init__(flip_at_start=flip_at_start, color=color, **kwargs)


class Mathematician(PiCreature):
    def __init__(self, color=GREY, **kwargs):
        super().__init__(color=color, **kwargs)


class BabyPiCreature(PiCreature):
    def __init__(self, scale_factor=0.5, eye_scale_factor=1.2, pupil_scale_factor=1.3, *args, **kwargs):
        PiCreature.__init__(self, *args, **kwargs)
        self.scale_factor = scale_factor
        self.eye_scale_factor = eye_scale_factor
        self.pupil_scale_factor = pupil_scale_factor
        self.scale(self.scale_factor)
        self.shift(LEFT)
        self.to_edge(DOWN, buff=LARGE_BUFF)
        eyes = VGroup(self.eyes, self.pupils)
        eyes_bottom = eyes.get_bottom()
        eyes.scale(self.eye_scale_factor)
        eyes.move_to(eyes_bottom, aligned_edge=DOWN)
        looking_direction = self.get_looking_direction()
        for pupil in self.pupils:
            pupil.scale(self.pupil_scale_factor)
        self.look(looking_direction)


class TauCreature(PiCreature):
    def __init__(self, **kwargs):
        super().__init__(file_name_prefix="TauCreatures")


class ThreeLeggedPiCreature(PiCreature):
    def __init__(self, **kwargs):
        super().__init__(file_name_prefix="ThreeLeggedPiCreatures")


class Eyes(VMobject):
    def __init__(self, body, eye_height=0.3, thing_to_look_at=None, mode="plain", **kwargs):
        VMobject.__init__(self, **kwargs)
        self.eye_height = eye_height
        self.thing_to_look_at = thing_to_look_at
        self.mode = mode
        self.body = body
        eyes = self.create_eyes()
        self.become(eyes, copy_submobjects=False)

    def create_eyes(self, mode=None, thing_to_look_at=None):
        if mode is None:
            mode = self.mode
        if thing_to_look_at is None:
            thing_to_look_at = self.thing_to_look_at
        self.thing_to_look_at = thing_to_look_at
        self.mode = mode
        looking_direction = None

        pi = PiCreature(mode=mode)
        eyes = VGroup(pi.eyes, pi.pupils)
        # if self.submobjects:
        #     eyes.match_height(self)
        #     eyes.move_to(self, DOWN)
        #     print("aaa")
        #     looking_direction = self[1].get_center() - self[0].get_center()
        # else:
        eyes.set_height(self.eye_height)
        eyes.move_to(self.body.get_top(), DOWN)

        height = eyes.get_height()
        if thing_to_look_at is not None:
            pi.look_at(thing_to_look_at)
        elif looking_direction is not None:
            pi.look(looking_direction)
        eyes.set_height(height)

        return eyes

    def change_mode(self, mode, thing_to_look_at=None):
        new_eyes = self.create_eyes(
            mode=mode,
            thing_to_look_at=thing_to_look_at
        )
        new_eyes.move_to(self)
        self.become(new_eyes, copy_submobjects=False)
        return self

    def look_at(self, thing_to_look_at):
        self.change_mode(
            self.mode,
            thing_to_look_at=thing_to_look_at
        )
        return self

    def blink(self, **kwargs):  # TODO, change Blink
        bottom_y = self.get_bottom()[1]
        for submob in self:
            submob.apply_function(
                lambda p: [p[0], bottom_y, p[2]]
            )
        return self


class PiCreatureGroup(VGroup):
    def __init__(self, row_width=3, col_height=2, **kwargs):
        VGroup.__init__(self, **kwargs)
        self.row_width = row_width
        self.col_height = col_height
        for i in range(self.row_width):
            for j in range(self.col_height):
                pi = PiCreature().scale(0.3)
                pi.move_to(i * DOWN + j * RIGHT)
                self.add(pi)


class Yan(VGroup):
    def __init__(self, height=2.5, color=BLUE, have_eye=True):
        super(Yan, self).__init__()
        self.yan_height = height
        self.yan_width = height*0.8
        self.head = RoundedRectangle(width=self.yan_width, height=height / 5, corner_radius=0.2,
                                     fill_opacity=1, fill_color=color).scale(0.8)
        self.eyes = Eyes(self.head).shift(UP*MED_SMALL_BUFF)
        self.mouth = Line(self.head.get_center() + self.yan_width / 9 * LEFT,
                          self.head.get_center() + self.yan_width / 9 * RIGHT, color=BLACK,
                          stroke_width=6, stroke_color=BLACK, stroke_opacity=1)
        self.leg = RoundedRectangle(width=self.yan_width / 4, height=height / 1.5, corner_radius=0.2,
                                    fill_opacity=1, fill_color=color)
        self.leg.next_to(self.head.get_bottom(), direction=DOWN, buff=0)
        self.arm = Rectangle(width=self.yan_width, height=height / 6,
                             fill_opacity=1, fill_color=color, stroke_opacity=0)
        self.arm.next_to(self.leg.get_center(), direction=UP * 0.5)
        self.left_hand = ArcPolygon(self.arm.get_corner(UL) + 0.2 * UL, self.arm.get_corner(DL),
                                    self.arm.get_corner(UL),
                                    color=color, fill_opacity=1, arc_config={'radius': 0.5, 'stroke_opacity': 0})
        self.right_hand = ArcPolygon(self.arm.get_corner(UR), self.arm.get_corner(DR),
                                     self.arm.get_corner(UR) + 0.2 * UR,
                                     color=color, fill_opacity=1, arc_config={'radius': 0.5, 'stroke_opacity': 0})
        """
        self.hand = self.arm.copy().pointwise_become_partial(body, *alpha_range)
            for alpha_range in (self.right_arm_range, self.left_arm_range)
        """
        self.add(self.head, self.arm, self.leg, self.left_hand, self.right_hand)
        if have_eye:
            self.add(self.eyes)
        self.add(self.mouth)
        self.month_stoke_width = 5

    def blink(self):
        eye_parts = self.eyes
        eye_bottom_y = eye_parts.get_bottom()[1]
        eye_parts.apply_function(
            lambda p: [p[0], eye_bottom_y, p[2]]
        )
        return self

    def emo(self, emotion):
        mouth_parts = self.mouth
        # Instead of what is drawn, make new mouth.
        if -0.1 < emotion < 0.1:
            new_mouth = Line(self.head.get_center() + self.yan_width / 9 * LEFT,
                             self.head.get_center() + self.yan_width / 9 * RIGHT, color=BLACK,
                             stroke_width=self.month_stoke_width, stroke_color=BLACK, stroke_opacity=1)
        else:
            new_mouth = ArcBetweenPoints(start=mouth_parts.get_left(), end=mouth_parts.get_right(),
                                         angle=PI * emotion, start_angle=PI * emotion / 2,
                                         stroke_width=self.month_stoke_width, color=BLACK)
        new_mouth.move_to(mouth_parts)
        mouth_parts.become(new_mouth)
        return self

    def look_at(self, thing_to_look_at):
        # Note to return self
        self.eyes.become(self.eyes.look_at(thing_to_look_at))
        return self

    def get_bubble(self, *content, **kwargs):
        bubble_class = kwargs.get("bubble_class", ThoughtBubble)
        bubble = bubble_class(**kwargs)
        if len(content) > 0:
            if isinstance(content[0], str):
                content_mob = Text(*content)
            else:
                content_mob = content[0]
            bubble.add_content(content_mob)
            if "height" not in kwargs and "width" not in kwargs:
                bubble.resize_to_content()
        bubble.pin_to(self)
        self.bubble = bubble
        return bubble


class SmallYan(Yan):
    def __init__(self, width=0.5, height=0.5, color=BLUE, have_eye=True):
        super().__init__()
        self.yan_width = width
        self.yan_height = height
        self.remove(self.head, self.arm, self.leg, self.left_hand, self.right_hand, self.mouth)
        if have_eye:
            self.remove(self.eyes)
        self.head = RoundedRectangle(width=width, height=height / 3, corner_radius=0,
                                     fill_opacity=1, fill_color=color, stroke_opacity=0).scale(0.8)
        self.month_stoke_width = 3
        self.mouth = Line(self.head.get_center() + width / 8 * LEFT, self.head.get_center() + width / 8 * RIGHT,
                          color=BLACK,
                          stroke_width=4, stroke_color=BLACK, stroke_opacity=1)
        self.eyes = Eyes(self.head, eye_height=0.2)
        self.leg = RoundedRectangle(width=width / 4, height=height / 1.5, corner_radius=0,
                                    fill_opacity=1, fill_color=color, stroke_opacity=0)
        self.leg.next_to(self.head.get_bottom(), direction=DOWN, buff=0)
        self.arm = RoundedRectangle(width=width, height=height / 6, corner_radius=0,
                                    fill_opacity=1, fill_color=color, stroke_opacity=0)
        self.arm.next_to(self.leg.get_center(), direction=ORIGIN)
        self.add(self.head, self.arm, self.leg, self.mouth)
        if have_eye:
            self.add(self.eyes)


class YanBodyPart(enum.IntEnum):
    HEAD = 0
    ARM = 1
    LEG = 2
    MOUTH = 3
    EYE_BALL_R = 4
    EYE_BALL_L = 5
    PUPIL_R = 6
    PUPIL_L = 7


class SVGYan(SVGMobject):
    def __init__(self, mode="plain", color=BLUE_E, file_name_prefix="Yan", stroke_width=0,
                 stroke_color=BLACK, fill_opacity=1.0, height=3, corner_scale_factor=0.75,
                 flip_at_start=False, is_looking_direction_purposeful=False, start_corner=None,
                 pupil_to_eye_width_ratio=0.4,
                 pupil_dot_to_pupil_width_ratio=0.3, **kwargs):
        self.mode = mode
        self.file_name_prefix = file_name_prefix
        self.corner_scale_factor = corner_scale_factor
        self.flip_at_start = flip_at_start
        self.is_looking_direction_purposeful = is_looking_direction_purposeful
        self.start_corner = start_corner
        # Range of proportions along body where arms are
        self.right_arm_range = [0.55, 0.7]
        self.left_arm_range = [.34, .462]
        self.pupil_to_eye_width_ratio = pupil_to_eye_width_ratio
        self.pupil_dot_to_pupil_width_ratio = pupil_dot_to_pupil_width_ratio

        self.parts_named = False

        svg_file = os.path.join(
            PI_CREATURE_DIR, "%s_%s.svg" % (self.file_name_prefix, mode)
        )
        if not os.path.exists(svg_file):
            warnings.warn("No %s design with mode %s" %
                          (self.file_name_prefix, mode))
            # TODO, this needs to change to a different, better directory
            svg_file = os.path.join(
                SVG_DIR,
                "Yan_plain.svg",
            )
        SVGMobject.__init__(self, file_name=svg_file, height=height, stroke_width=stroke_width,
                            fill_opacity=fill_opacity, color=color, stroke_color=stroke_color,
                            **kwargs)

        if self.flip_at_start:
            self.flip()
        if self.start_corner is not None:
            self.to_corner(self.start_corner)

    def align_data(self, mobject, skip_point_alignment=True):
        # This ensures that after a transform into a different mode,
        # the pi creatures mode will be updated appropriately
        SVGMobject.align_data(self, mobject, skip_point_alignment=True)
        if isinstance(mobject, PiCreature):
            self.mode = mobject.get_mode()

    def name_parts(self):
        self.mouth = self.submobjects[YanBodyPart.MOUTH]
        self.arm = self.submobjects[YanBodyPart.ARM]
        self.head = self.submobjects[YanBodyPart.HEAD]
        self.leg = self.submobjects[YanBodyPart.LEG]
        self.pupils = VGroup(*[
            self.submobjects[YanBodyPart.PUPIL_L],
            self.submobjects[YanBodyPart.PUPIL_R]
        ])
        self.eyes = VGroup(*[
            self.submobjects[YanBodyPart.EYE_BALL_L],
            self.submobjects[YanBodyPart.EYE_BALL_R]
        ])
        self.eye_parts = VGroup(self.eyes, self.pupils)
        self.parts_named = True

    def init_colors(self):
        SVGMobject.init_colors(self)
        if not self.parts_named:
            self.name_parts()
        self.mouth.set_fill(BLACK, opacity=1)
        self.arm.set_fill(self.color, opacity=1)
        self.head.set_fill(self.color, opacity=1)
        self.leg.set_fill(self.color, opacity=1)
        self.eyes.set_fill(WHITE, opacity=1)
        # self.eyes.set(height=0.1*self.height)
        # self.init_pupils()
        return self

    def init_pupils(self):
        # Instead of what is drawn, make new circles.
        # This is mostly because the paths associated
        # with the eyes in all the drawings got slightly
        # messed up.
        for eye, pupil in zip(self.eyes, self.pupils):
            pupil_r = eye.get_width() / 2
            pupil_r *= self.pupil_to_eye_width_ratio
            dot_r = pupil_r
            dot_r *= self.pupil_dot_to_pupil_width_ratio

            new_pupil = Circle(
                radius=pupil_r,
                color=BLACK,
                fill_opacity=1,
                stroke_width=0,
            )
            dot = Circle(
                radius=dot_r,
                color=WHITE,
                fill_opacity=1,
                stroke_width=0,
            )
            new_pupil.move_to(pupil)
            pupil.become(new_pupil)
            dot.shift(
                new_pupil.get_boundary_point(UL) -
                dot.get_boundary_point(UL)
            )
            pupil.add(dot)

    def copy(self):
        copy_mobject = SVGMobject.copy(self)
        copy_mobject.name_parts()
        return copy_mobject

    def set_color(self, color):
        self.arm.set_fill(color)
        self.leg.set_fill(color)
        self.head.set_fill(color)
        self.color = color
        return self

    def change_mode(self, mode):
        new_self = self.__class__(
            mode=mode
        )
        new_self.match_style(self)
        new_self.match_height(self)
        if self.is_flipped() != new_self.is_flipped():
            new_self.flip()
        new_self.shift(self.eyes.get_center() - new_self.eyes.get_center())
        if hasattr(self, "purposeful_looking_direction"):
            new_self.look(self.purposeful_looking_direction)
        self.become(new_self)
        self.mode = mode
        return self

    def get_mode(self):
        return self.mode

    def look(self, direction):
        norm = get_norm(direction)
        if norm == 0:
            return
        direction /= norm
        self.purposeful_looking_direction = direction
        for pupil, eye in zip(self.pupils.split(), self.eyes.split()):
            c = eye.get_center()
            right = eye.get_right() - c
            up = eye.get_top() - c
            vect = direction[0] * right + direction[1] * up
            v_norm = get_norm(vect)
            p_radius = 0.5 * pupil.get_width()
            vect *= (v_norm - 0.75 * p_radius) / v_norm
            pupil.move_to(c + vect)
        self.pupils[1].align_to(self.pupils[0], DOWN)
        return self

    def look_at(self, point_or_mobject):
        if isinstance(point_or_mobject, Mobject):
            point = point_or_mobject.get_center()
        else:
            point = point_or_mobject
        self.look(point - self.eyes.get_center())
        return self

    def change(self, new_mode, look_at_arg=None):
        self.change_mode(new_mode)
        if look_at_arg is not None:
            self.look_at(look_at_arg)
        return self

    def get_looking_direction(self):
        vect = self.pupils.get_center() - self.eyes.get_center()
        return normalize(vect)

    def get_look_at_spot(self):
        return self.eyes.get_center() + self.get_looking_direction()

    def is_flipped(self):
        return self.eyes.submobjects[0].get_center()[0] > \
               self.eyes.submobjects[1].get_center()[0]

    def blink(self):
        eye_parts = self.eye_parts
        eye_bottom_y = eye_parts.get_bottom()[1]
        eye_parts.apply_function(
            lambda p: [p[0], eye_bottom_y, p[2]]
        )
        return self

    def to_corner(self, vect=None, **kwargs):
        if vect is not None:
            SVGMobject.to_corner(self, vect, **kwargs)
        else:
            self.scale(self.corner_scale_factor)
            self.to_corner(DOWN + LEFT, **kwargs)
        return self

    def get_bubble(self, *content, **kwargs):
        bubble_class = kwargs.get("bubble_class", ThoughtBubble)
        bubble = bubble_class(**kwargs)
        if len(content) > 0:
            if isinstance(content[0], str):
                content_mob = Text(*content)
            else:
                content_mob = content[0]
            bubble.add_content(content_mob)
            if "height" not in kwargs and "width" not in kwargs:
                bubble.resize_to_content()
        bubble.pin_to(self)
        self.bubble = bubble
        return bubble

    def make_eye_contact(self, pi_creature):
        self.look_at(pi_creature.eyes)
        pi_creature.look_at(self.eyes)
        return self

    def shrug(self):
        self.change_mode("shruggie")
        top_mouth_point, bottom_mouth_point = [
            self.mouth.points[np.argmax(self.mouth.points[:, 1])],
            self.mouth.points[np.argmin(self.mouth.points[:, 1])]
        ]
        self.look(top_mouth_point - bottom_mouth_point)
        return self

    def get_arm_copies(self):
        arm = self.arm
        return VGroup(*[
            arm.copy().pointwise_become_partial(arm, *alpha_range)
            for alpha_range in (self.right_arm_range, self.left_arm_range)
        ])

    def smile(self, smile_mode):
        mouth_parts = self.mouth
        mouth_radius = mouth_parts.get_width() / 2
        # Instead of what is drawn, make new mouth.
        if smile_mode == 0:
            new_mouth = ArcBetweenPoints(start=mouth_parts.get_left(), end=mouth_parts.get_right(),
                                         color=BLACK, radius=4 * mouth_radius)
        else:
            ang = -PI if smile_mode == 1 else PI
            new_mouth = Arc(radius=mouth_radius, angle=ang, stroke_width=5, color=BLACK)
        new_mouth.move_to(mouth_parts)
        mouth_parts.become(new_mouth)
        return self